package genedit.aStar;

import genedit.model.Workspace;
import genedit.view.graph.ElementPainter;
import genedit.view.graph.LinkPainter;

import java.awt.geom.Point2D;
import java.util.Iterator;

/**
 * Klasa opisuje prostor stanja koji se pretražuje da bi se dobila najkraća veza
 * između dva elementa tako da ne seče ni jedan grafički element izuzev drugih
 * veza.
 * 
 * @author Igić
 * @version 1.3 15/12/13
 */
public class SearchMap {

	/**
	 * Širina prostora pretrage.
	 */
	private double mapWith;
	/**
	 * Visina prostora pretrage.
	 */
	private double mapHeight;
	/**
	 * Aproksimirani prostor pretrage.
	 */
	private Node[][] map;
	/**
	 * Startna lokacija.
	 */
	private Point2D startLocation;
	/**
	 * Početna lokacija prostora pretrage.
	 */
	private Point2D startMap;
	/**
	 * Krajnja lokacija prostora pretrage.
	 */
	private Point2D goalLocation;

	public SearchMap(double mapWith, double mapHeight, Point2D startMap, Point2D startLocation,
			Point2D goalLocation) {

		this.mapWith = mapWith;
		this.mapHeight = mapHeight;
		this.startMap = startMap;
		this.startLocation = startLocation;
		this.goalLocation = goalLocation;

		createMap();

	}

	/**
	 * Metoda kreira prostor stanja statičkih dimenzija.
	 */
	private void createMap() {
		Node node;
		map = new Node[100][100];
		for (int x = 0; x < 100; x++) {
			for (int y = 0; y < 100; y++) {
				// povećavamo ćelije u odnosu na širinu i dužinu našeg prostora.
				// Što je veći prostor, nepreciznija je pretraga i postavljamo
				// prepreke
				node = new Node(new Point2D.Double(startMap.getX() + x * (mapWith / 100),
						startMap.getY() + y * (mapHeight / 100)));
				if ((getElementAtPosition(node.position) != null && !((getElementAtPosition(node.position) instanceof LinkPainter) && numberOfPainters(node.position) == 0)))
					if (!getElementAtPosition(startLocation).equals(
							getElementAtPosition(node.position))
							&& !getElementAtPosition(goalLocation).equals(
									getElementAtPosition(node.position)))
						node.setObstacle(true);

				map[x][y] = node;
				if (!(y == 0)) {
					map[x][y].setNorth(map[x][y - 1]);
					map[x][y - 1].setSouth(map[x][y]);
				}
				if (!(x == 0)) {
					map[x][y].setWest(map[x - 1][y]);
					map[x - 1][y].setEast(map[x][y]);
				}
			}
		}
	}

	/**
	 * Pronalazi ivice prostora stanja i definise ih.
	 */
	private void registerEdges() {
		for (int x = 0; x < (int) mapWith; x++) {
			for (int y = 0; y < (int) mapHeight; y++) {
				if (!(y == 0))
					map[x][y].setNorth(map[x][y - 1]);
				if (!(x == (int) mapWith - 1))
					map[x][y].setEast(map[x + 1][y]);
				if (!(y == (int) mapHeight - 1))
					map[x][y].setSouth(map[x][y + 1]);
				if (!(x == 0))
					map[x][y].setWest(map[x - 1][y]);

			}
		}
	}

	public Node[][] getNodes() {
		return map;
	}

	public void setObstical(double x, double y, boolean isObstical) {
		map[(int) (x - startMap.getX())][((int) (y - startMap.getY()))].setObstacle(isObstical);
	}

	public Node getNode(double x, double y) {
		// pokriva slučaj za samu ivicu prostora
		if (x - startMap.getX() == mapWith && y - startMap.getY() == mapHeight)
			return map[99][99];
		else if (x - startMap.getX() == mapWith)
			return map[99][(int) (100 * (y - startMap.getY()) / mapHeight)];
		else if (y - startMap.getY() == mapHeight)
			return map[(int) (100 * (x - startMap.getX()) / mapWith)][99];
		return map[(int) (100 * Math.abs(x - startMap.getX()) / mapWith)][(int) (100 * Math.abs(y
				- startMap.getY()) / mapHeight)];
	}

	public Point2D getStartMap() {
		return startMap;
	}

	public void setStartMap(Point2D startMap) {
		this.startMap = startMap;
	}

	public void setStartLocation(Point2D startLocation) {
		this.startLocation = startLocation;
	}

	public void setGoalLocation(Point2D goalLocation) {
		this.goalLocation = goalLocation;
	}

	public Point2D getStartLocation() {
		return startLocation;
	}

	public Node getStartNode() {
		if (startLocation.getX() - startMap.getX() == mapWith
				&& startLocation.getY() - startMap.getY() == mapHeight)
			return map[99][99];
		else if (startLocation.getX() - startMap.getX() == mapWith)
			return map[99][(int) (100 * (startLocation.getY() - startMap.getY()) / mapHeight)];
		else if (startLocation.getY() - startMap.getY() == mapHeight)
			return map[(int) (100 * (startLocation.getX() - startMap.getX()) / mapWith)][99];
		return map[(int) (100 * (startLocation.getX() - startMap.getX()) / mapWith)][(int) (100 * (startLocation
				.getY() - startMap.getY()) / mapHeight)];
	}

	public Point2D getGoalLocation() {
		return goalLocation;
	}

	public Node getGoalNode() {
		if (goalLocation.getX() - startMap.getX() == mapWith
				&& goalLocation.getY() - startMap.getY() == mapHeight)
			return map[99][99];
		else if (goalLocation.getX() - startMap.getX() == mapWith)
			return map[99][(int) (100 * (goalLocation.getY() - startMap.getY()) / mapHeight)];
		else if (goalLocation.getY() - startMap.getY() == mapHeight)
			return map[(int) (100 * (goalLocation.getX() - startMap.getX()) / mapWith)][99];
		return map[(int) (100 * (goalLocation.getX() - startMap.getX()) / mapWith)][(int) (100 * (goalLocation
				.getY() - startMap.getY()) / mapHeight)];
	}

	/**
	 * Metoda izračunava udaljenost između dva čvora.
	 * 
	 * @param node1
	 *            prvi čvor
	 * @param node2
	 *            drugi čvor
	 * @return razdaljina između parametara
	 */
	public double getDistanceBetween(Node node1, Node node2) {
		if (node1.getPreviousNode() != null) // zabranjujemo definisanje
												// prelomnih tačaka unutar
												// početnog i krajnjeg elementa
			if (((node1.getPreviousNode().getPosition().getX() == node1.getPosition().getX()
					&& node1.getPreviousNode().getPosition().getY() != node1.getPosition().getY()
					&& node1.getPosition().getX() != node2.getPosition().getX() && node1
					.getPosition().getY() == node2.getPosition().getY()) || (node1
					.getPreviousNode().getPosition().getX() != node1.getPosition().getX()
					&& node1.getPreviousNode().getPosition().getY() == node1.getPosition().getY()
					&& node1.getPosition().getX() == node2.getPosition().getX() && node1
					.getPosition().getY() != node2.getPosition().getY()))
					&& ((getElementAtPosition(node2.position) != null && getElementAtPosition(
							node2.position).equals(getElementAtPosition(startLocation))) || (getElementAtPosition(node1.position) != null && getElementAtPosition(
							node1.position).equals(getElementAtPosition(goalLocation))))) {
				return 200 * (mapHeight + mapWith);
			} // naglašavamo da želimo da imamo što manje prelomnih tačaka
			else if ((node1.getPreviousNode().getPosition().getX() == node1.getPosition().getX()
					&& node1.getPreviousNode().getPosition().getY() != node1.getPosition().getY()
					&& node1.getPosition().getX() != node2.getPosition().getX() && node1
					.getPosition().getY() == node2.getPosition().getY())
					|| (node1.getPreviousNode().getPosition().getX() != node1.getPosition().getX()
							&& node1.getPreviousNode().getPosition().getY() == node1.getPosition()
									.getY()
							&& node1.getPosition().getX() == node2.getPosition().getX() && node1
							.getPosition().getY() != node2.getPosition().getY())) {
				return 50 * (mapHeight + mapWith);
			} // zabranjujemo da prelazi preko krajnjeg elementa, što je radio u
				// nekim slučajevima jer ne predstavlja prepreku
			else if ((getElementAtPosition(node2.position) == null || !getElementAtPosition(
					node2.position).equals(getElementAtPosition(goalLocation)))
					&& (getElementAtPosition(node1.position) != null && getElementAtPosition(
							node1.position).equals(getElementAtPosition(goalLocation)))) {
				return 200 * (mapHeight + mapWith);
			}
			// ukoliko možemo, odvajamo malo od ivice prepreke
			else if ((getElementAtPosition(new Point2D.Double(node2.position.getX() + 30,
					node2.position.getY())) != null && !((getElementAtPosition(new Point2D.Double(
					node2.position.getX() + 30, node2.position.getY())) instanceof LinkPainter) && numberOfPainters(new Point2D.Double(
					node2.position.getX() + 30, node2.position.getY())) == 0)))
				if (!getElementAtPosition(startLocation).equals(
						getElementAtPosition(new Point2D.Double(node2.position.getX() + 30,
								node2.position.getY())))
						&& !getElementAtPosition(goalLocation).equals(
								getElementAtPosition(new Point2D.Double(node2.position.getX() + 30,
										node2.position.getY())))) {
					return 100 * (mapHeight + mapWith);
				} else if ((getElementAtPosition(new Point2D.Double(node2.position.getX(),
						node2.position.getY() + 30)) != null && !((getElementAtPosition(new Point2D.Double(
						node2.position.getX(), node2.position.getY() + 30)) instanceof LinkPainter) && numberOfPainters(new Point2D.Double(
						node2.position.getX(), node2.position.getY() + 30)) == 0)))
					if (!getElementAtPosition(startLocation).equals(
							getElementAtPosition(new Point2D.Double(node2.position.getX(),
									node2.position.getY() + 30)))
							&& !getElementAtPosition(goalLocation).equals(
									getElementAtPosition(new Point2D.Double(node2.position.getX(),
											node2.position.getY() + 30)))) {
						return 100 * (mapHeight + mapWith);
					} else if ((getElementAtPosition(new Point2D.Double(node2.position.getX() - 30,
							node2.position.getY())) != null && !((getElementAtPosition(new Point2D.Double(
							node2.position.getX() - 30, node2.position.getY())) instanceof LinkPainter) && numberOfPainters(new Point2D.Double(
							node2.position.getX() - 30, node2.position.getY())) == 0)))
						if (!getElementAtPosition(startLocation).equals(
								getElementAtPosition(new Point2D.Double(node2.position.getX() - 30,
										node2.position.getY())))
								&& !getElementAtPosition(goalLocation).equals(
										getElementAtPosition(new Point2D.Double(node2.position
												.getX() - 30, node2.position.getY())))) {
							return 100 * (mapHeight + mapWith);
						} else if ((getElementAtPosition(new Point2D.Double(node2.position.getX(),
								node2.position.getY() - 30)) != null && !((getElementAtPosition(new Point2D.Double(
								node2.position.getX(), node2.position.getY() - 30)) instanceof LinkPainter) && numberOfPainters(new Point2D.Double(
								node2.position.getX(), node2.position.getY() - 30)) == 0)))
							if (!getElementAtPosition(startLocation).equals(
									getElementAtPosition(new Point2D.Double(node2.position.getX(),
											node2.position.getY() - 30)))
									&& !getElementAtPosition(goalLocation).equals(
											getElementAtPosition(new Point2D.Double(node2.position
													.getX(), node2.position.getY() - 30)))) {
								return 100 * (mapHeight + mapWith);
							} else {
								return 1;
							}
						else
							return 1;
		return 1;
	}

	public double getMapWith() {
		return mapWith;
	}

	public double getMapHeight() {
		return mapHeight;
	}

	public void clear() {
		startLocation.setLocation(0, 0);
		goalLocation.setLocation(0, 0);
		createMap();
		registerEdges();
	}

	/**
	 * Metoda vraća painter elementa na zadatoj poziciji.
	 * 
	 * @param position
	 *            tačka na kanvasu
	 * @return painter na datoj poziciji
	 */
	private ElementPainter getElementAtPosition(Point2D position) {

		Iterator<ElementPainter> iter = Workspace.getInstance().getActiveDocument()
				.getIteratorElementPainters();
		while (iter.hasNext()) {
			ElementPainter painter = iter.next();
			if (painter.isElementAt(position))
				return painter;
		}

		return null;
	}

	/**
	 * Metoda vraća broj paintera u datoj tački koji nisu instanca klase
	 * LinkPainter.
	 * 
	 * @param position
	 *            tačka na kanvasu
	 * @return broj paintera u datoj tački
	 */
	private int numberOfPainters(Point2D position) {
		int i = 0;
		Iterator<ElementPainter> iter = Workspace.getInstance().getActiveDocument()
				.getIteratorElementPainters();
		while (iter.hasNext()) {
			ElementPainter painter = iter.next();
			if (painter.isElementAt(position) && !(painter instanceof LinkPainter))
				i++;
		}

		return i;
	}

}
